#
#
#
# TODO:
#	should we look at signals etc. for enums/flags?
#	we're getting warnings about unregistered types with new enums/flags 
#	stuff, quell them.
#

package Glib::GenPod;

our $VERSION = '0.03';

use strict;
use warnings;
use Carp;
use File::Spec;
use Data::Dumper;
use POSIX qw(strftime);

use Glib;

use base 'Exporter';

our @EXPORT = qw(
	add_types
	xsdoc2pod
	podify_properties
	podify_values
	podify_signals
	podify_ancestors
	podify_interfaces
	podify_methods
	podify_enums_and_flags
	podify_deprecated_by
);

our $COPYRIGHT = undef;
our $AUTHORS = 'Gtk2-Perl Team';
our $MAIN_MOD = undef;
our $YEAR = strftime "%Y", gmtime;

our ($xspods, $data);
	
=head1 NAME

Glib::GenPod - POD generation utilities for Glib-based modules

=head1 SYNOPSIS

 use Glib::GenPod;

 # use the defaults:
 xsdoc2pod ($xsdocparse_output_file, $destination_dir);

 # or take matters into your own hands
 require $xsdocparse_output_file;
 foreach my $package (sort keys %$data) {
     print "=head1 NAME\n\n$package\n\n";
     print "=head1 METHODS\n\n" . podify_methods ($package) . "\n\n";
 }

=head1 DESCRIPTION 

This module includes several utilities for creating pod for xs-based Perl
modules which build on the Glib module's foundations.  The most important bits
are the logic to convert the data structures created by xsdocparse.pl to
describe xsubs and pods into method docs, with call signatures and argument
descriptions, and converting C type names into Perl type names.  The rest of
the module is mostly boiler-plate code to format and pretty-print information
that may be queried from the Glib type system.

To make life easy for module maintainers, we also include a do-it-all function,
xsdoc2pod(), which does pretty much everything for you.  All of the pieces it
uses are publically usable, so you can do whatever you like if you don't like
the default output.

=head1 DOCUMENTING THE XS FILES

All of the information used as input to the methods included here comes from
the XS files of your project, and is extracted by Glib::ParseXSDoc's
C<xsdocparse>.  This function creates an file containing Perl code that may be
eval'd or require'd to recreate the parsed data structures, which are a list of
pods from the verbatim C portion of the XS file (the xs api docs), and a hash
of the remaining data, keyed by package name, and including the pods and xsubs
read from the rest of each XS file following the first MODULE line.

Several custom POD directives are recognized in the XSubs section.  Note that
each one is sought as a paragraph starter, and must follow a C<=cut> directive.

=over

=item =for object Package::Name

All xsubs and pod from here until the next object directive or MODULE line
will be placed under the key 'I<Package::Name>' in xsdocparse's data
structure.  Everything from this line to the next C<=cut> is included as a
description POD.

=item =for object Package::Name (Other::Package::Name)

Generate POD in I<Package::Name> but for the package I<Other::Package::Name>.
This is useful if you want POD to appear in a different namespace but still
want the automatically generated hierarchy, signal and property listing,
etc. from the original namespace.  For example:

  =for object Gnome2::PanelApplet::main (Gnome2::PanelApplet)
  =cut

This will create Gnome2/PanelApplet/main.pod containing the automatically
generated documentation for Gnome2::PanelApplet (hierarchy, signals, etc.) plus
the method listing from the current XS file.

=item =for enum Package::Name

=item =for flags Package::Name

This causes xsdoc2pod to call C<podify_values> on I<Package::Name> when
writing the pod for the current package (as set by an object directive or
MODULE line).  Any text in this paragraph, to the next C<=cut>, is included
in that section.

=item =for deprecated_by Package::Name

Used to add a deprecation warning, indicating I<Package::Name> as an
alternative way to achieve the same functionality.  There may be any number
these in each package.

=item =for see_also L<some_thing_to_see>

Used to add extra see alsos onto the end of the parents, if any, for a given
object.  Anything following the space behind see_also up to the end of the
line will be placed onto the list of "see also"s.  There may be any number of
these in each package.

=item =for apidoc

=item =for apidoc Full::Symbol::name

Paragraphs of this type document xsubs, and are associated with the xsubs
by xsdocparse.pl.  If the full symbol name is not included, the paragraph
must be attached to the xsub declaration (no blank lines between C<=cut> and
the xsub).

Within the apidoc PODs, we recognize a few special directives (the "for\s+"
is optional on these):

=over

=item =for signature ...

Override the generated call signature with the ... text.  If you include
multiple signature directives, they will all be used.  This is handy when
you want to change the return type or list different ways to invoke an
overloaded method, like this:

 =for apidoc

 =signature bool Class->foo

 =signature ($thing, @other) = $object->foo ($it, $something)

 Text in here is included in the generated documentation.
 You can actually include signature and arg directives
 at any point in this pod -- they are stripped after.
 In fact, any pod is valid in here, until the =cut.

 =cut
 void foo (...)
     PPCODE:
        /* crazy code follows */

=item =for arg name (type) description

=item =for arg name description

The arg directive adds or overrides an argument description.  The
description text is optional, as is the type specification (the part
in parentheses).  If you want to hide an argument, specify C<__hide__>
as its type.  The arg name does I<not> need to include a sigil,
as dollar signs will be added.  FIXME what about @ for lists?

=back

Also, we honor a couple of "modifiers" on the =for apidoc line, following the
symbol name, if present:

=over

=item - __hide__

Do not document this xsub.  This is handy in certain situations, e.g., for
private functions.  DESTROY always has this turned on, for example.

=item - __gerror__

This function or method can generate a Glib::Error exception.

=item - __function__

Generate a function-style signature for this xsub.  The default is to
generate method-style signatures.

=item - __deprecated__

This function or method is deprecated and should not be used in newly written
code.

=back

(These are actually handled by Glib::ParseXSDoc, but we list them here
because, well, they're an important part of how you document the XS files.)

=back

=head1 FUNCTIONS

=over

=cut

=item xsdoc2pod ($datafile, $outdir='blib/lib', index=undef)

Given a I<$datafile> containing the output of xsdocparse.pl, create in 
I<$outdir> a pod file for each package, containing everything we can think
of for that module.  Output is controlled by the C<=for object> directives
and such in the source code.

If you don't want each package to create a separate pod file, then use
this function's code as a starting point for your own pretty-printer.

=cut

sub xsdoc2pod
{
	my $datafile = shift();
	my $outdir   = shift() || 'blib/lib';
	my $index    = shift;

	mkdir $outdir unless (-d $outdir);

	die "usage: $0 datafile [outdir]\n"
		unless defined $datafile;

	require $datafile;

	my @files = ();

	my $pkgdata;
	my $ret;

	foreach my $package (sort { ($a->isa('Glib::Object') ? -1 : 1) } 
				keys %$data)
	{
		$pkgdata = $data->{$package};

		my $pod = File::Spec->catfile ($outdir, split /::/, $package)
		        . '.pod';
		my (undef, @dirs, undef) = File::Spec->splitpath ($pod);
		mkdir_p (File::Spec->catdir (@dirs));

		open POD, ">$pod" or die "can't open $pod for writing: $!\n";
		select POD;

		$package = $pkgdata->{object} if (exists $pkgdata->{object});

		preprocess_pod ($_) foreach (@{$pkgdata->{pods}});

		push @files, {
			name => $package,
			file => $pod,
			blurb => $pkgdata->{blurb},
		};

		# podify_pods() always returns proper POD with a =cut at the
		# end.  But all the other =head1 below need a closing =cut.

		print "=head1 NAME\n\n$package";
                if(exists ($pkgdata->{blurb})) {
		print ' - '.$pkgdata->{blurb};
                } elsif($package =~ m/^Gtk2::Pango/) {
                my $newname = $package;
                $newname =~ s/Gtk2:://;
                print ' - moved to ' . $newname . ', kept for backwards compatibility'
                } elsif(convert_to_cname($package)) {
                print ' - wrapper for '.convert_to_cname($package);
                }
		print "\n\n=cut\n\n";

		#                   pods            , position 
		$ret = podify_pods ($pkgdata->{pods}, 'SYNOPSIS');
		print "$ret\n\n" if ($ret);
		
		$ret = podify_pods ($pkgdata->{pods}, 'DESCRIPTION');
		print "$ret\n\n" if ($ret);
		
		my $parents;
		($ret, $parents) = podify_ancestors ($package);
		print "=head1 HIERARCHY\n\n$ret\n\n=cut\n\n" if ($ret);
		
		$ret = podify_pods ($pkgdata->{pods}, 'post_hierarchy');
		print "$ret\n\n" if ($ret);
		
		$ret = podify_interfaces ($package);
		print "=head1 INTERFACES\n\n$ret\n\n=cut\n\n" if ($ret);
		
		$ret = podify_pods ($pkgdata->{pods}, 'post_interfaces');
		print "$ret\n\n" if ($ret);

		$ret = podify_pods ($pkgdata->{pods});
		print "$ret\n\n" if ($ret);

		$ret = podify_deprecated_by ($package, @{ $pkgdata->{deprecated_bys} });
		print "\n=head1 DEPRECATION WARNING\n\n$ret\n\n=cut\n\n" if ($ret);

		$ret = podify_methods ($package, $pkgdata->{xsubs});
		print "\n=head1 METHODS\n\n$ret\n\n=cut\n\n" if ($ret);
		
		$ret = podify_pods ($pkgdata->{pods}, 'post_methods');
		print "$ret\n\n" if ($ret);

		$ret = podify_properties ($package);	
		print "\n=head1 PROPERTIES\n\n$ret\n\n=cut\n\n" if ($ret);

		$ret = podify_pods ($pkgdata->{pods}, 'post_properties');
		print "$ret\n\n" if ($ret);

		$ret = podify_signals ($package);	
		print "\n=head1 SIGNALS\n\n$ret\n\n=cut\n\n" if ($ret);

		$ret = podify_pods ($pkgdata->{pods}, 'post_signals');
		print "$ret\n\n" if ($ret);

		$ret = podify_enums_and_flags ($pkgdata, $package);	
		print "\n=head1 ENUMS AND FLAGS\n\n$ret\n\n=cut\n\n" if ($ret);

		$ret = podify_pods ($pkgdata->{pods}, 'post_enums');
		print "$ret\n\n" if ($ret);

		$ret = podify_pods ($pkgdata->{pods}, 'SEE_ALSO');
		if ($ret)
		{
			print "$ret\n\n";
		}
		else
		{
			# don't link to yourself
			pop @$parents;
			# link to the toplevel, if we can.
			unshift @$parents, $MAIN_MOD if $MAIN_MOD;

			$ret = podify_see_alsos (@$parents,
			                         $pkgdata->{see_alsos}
						 ? @{ $pkgdata->{see_alsos} }
			                         : ());
			print "\n=head1 SEE ALSO\n\n$ret\n\n=cut\n\n" if ($ret);
		}

		$ret = podify_pods ($pkgdata->{pods}, 'COPYRIGHT');
		if ($ret)
		{
			# copyright over-ridden
			print "$ret\n\n" 
		}
		else
		{
			# use normal copyright system
			$ret = get_copyright ();
			print "\n=head1 COPYRIGHT\n\n$ret\n\n=cut\n\n" if ($ret);
		}

		close POD;
	}

	if ($index) {
		open INDEX, ">$index"
			or die "can't open $index for writing: $!\b";
		select INDEX;

		foreach (sort {$a->{name} cmp $b->{name}} @files) {
			print join("\t", $_->{file},
				   $_->{name},
				   $_->{blurb} ? $_->{blurb} : () ) . "\n";
		}
		
		close INDEX;
	}
}

# more sensible names for the basic types
our %basic_types = (
	# the perl wrappers for the GLib fundamentals
	'Glib::Scalar'  => 'scalar',
	'Glib::String'  => 'string',
	'Glib::Int'     => 'integer',
	'Glib::Uint'    => 'unsigned',
	'Glib::Double'  => 'double',
	'Glib::Boolean' => 'boolean',

	# sometimes we can get names that are already mapped...
	# e.g., from =for arg lines.  pass them unbothered.
	scalar     => 'scalar',
	subroutine => 'subroutine',
	integer    => 'integer',
	string     => 'string',
	package    => 'package',
	list       => 'list',

	# other C names which may sneak through
	bool     => 'boolean', # C++ keyword, but provided by the perl api
	boolean  => 'boolean',
	int      => 'integer',
	char     => 'integer',
	uint     => 'unsigned',
	float    => 'double',
	double   => 'double',
	char     => 'string',
	unsigned => 'unsigned',

	gboolean => 'boolean',
	gint     => 'integer',
	gint8    => 'integer',
	gint16   => 'integer',
	gint32   => 'integer',
	guint8   => 'unsigned',
	guint16  => 'unsigned',
	guint32  => 'unsigned',
	gulong   => 'unsigned',
	gshort   => 'integer',
	guint    => 'integer',
	gushort  => 'unsigned',
	gfloat   => 'double',
	gdouble  => 'double',
	gchar    => 'string',

	SV       => 'scalar',
	UV       => 'unsigned',
	IV       => 'integer',
	CV       => 'subroutine',

	gchar_length => 'string',

	FILE => 'file handle',
	time_t => 'unix timestamp',

	GPerlFilename	=> 'localized file name',
	GPerlFilename_const	=> 'localized file name',
);

unless (Glib->CHECK_VERSION (2, 4, 0)) {
	$basic_types{'Glib::Strv'} = 'ref to array of strings';
}

=item add_types (@filenames)

Parse the given I<@filenames> for entries to add to the C<%basic_types> used
for C type name to Perl package name mappings of types that are not registered
with the Glib type system.  The file format is dead simple: blank lines are
ignored; /#.*$/ is stripped from each line as comments; the first token on
each line is considered to be a C type name, and the remaining tokens are the
description of that type.  For example, a valid file may look like this:

  # a couple of special types
  FooBar      Foo::Bar
  Frob        localized frobnicator

C type decorations such as "const" and "*" are implied (do not include them),
and the _ornull variant is handled for you.

=cut

sub add_types {
	my @files = @_;
	foreach my $f (@files) {
		open IN, $f or die "can't open types file $f: $!\n";
		my $n = 0;
		while (<IN>) {
			chomp;
			s/#.*//;
			next if m/^\s*$/;
			my ($c_name, @bits) = split;
			if (@bits) {
				$basic_types{$c_name} = join ' ', @bits;
				$n++;
			} else {
				warn "$f:$.: no description for $c_name\n"
			}
		}
		print "Loaded $n extra types from $f\n";
		close IN;
	}
}


=item $string = podify_properties ($packagename)

Pretty-print the object properties owned by the Glib::Object derivative
I<$packagename> and return the text as a string.  Returns undef if there
are no properties or I<$package> is not a Glib::Object.

=cut

sub podify_properties {
	my $package = shift;
	my @properties;
	eval { @properties = Glib::Object::list_properties($package); 1; };
	return undef unless (@properties or not $@);

	# we have a non-zero number of properties, but there may still be
	# none for this particular class.  keep a count of how many
	# match this class, so we can return undef if there were none.
	my $nmatch = 0;
	my $str = "=over\n\n";
	foreach my $p (sort { $a->{name} cmp $b->{name} } @properties) {
		next unless $p->{owner_type} eq $package;
		++$nmatch;
		my $stat = join " / ",  @{ $p->{flags} };
		my $type = exists $basic_types{$p->{type}}
		      ? $basic_types{$p->{type}}
		      : $p->{type};
		$str .= "=item '$p->{name}' ($type : $stat)\n\n";
		$str .= "$p->{descr}\n\n" if (exists ($p->{descr}));
	}
	$str .= "=back\n\n";

	return $nmatch ? $str : undef;
}

=item $string = podify_values ($packagename)

List and pretty-print the values of the GEnum or GFlags type I<$packagename>,
and return the text as a string.  Returns undef if I<$packagename> isn't an
enum or flags type.

=cut

sub podify_values {
	my $package = shift;
	my @values;
	eval { @values = Glib::Type->list_values ($package); 1; };
	return undef unless (@values or not $@);

	return "=over\n\n"
	     . join ("\n\n", map { "=item * '$_->{nick}' / '$_->{name}'" } @values)
	     . "\n\n=back\n\n";
}

=item $string = podify_signals ($packagename)

Query, list, and pretty-print the signals associated with I<$packagename>.
Returns the text as a string, or undef if there are no signals or
I<$packagename> is not a Glib::Object derivative.

=cut

sub podify_signals {
    my $str = undef;
    eval {
	my @sigs = Glib::Type->list_signals (shift);
	return undef unless @sigs;
	$str = "=over\n\n";
	foreach (@sigs) {
		$str .= '=item ';
		$str .= convert_type ($_->{return_type}).' = '
			if exists $_->{return_type};
		$str .= "B<$_->{signal_name}> (";
		$str .= join ', ', map { convert_type ($_) }
				$_->{itype}, @{$_->{param_types}};
		$str .= ")\n\n";
	}
	$str .= "=back\n\n";
    };
    return $str
}

=item $string = podify_deprecated_by ($packagename, @deprecated_by)

Creates a deprecation warning for $packagename, suggesting using the items
inside @deprecated_by instead.

=cut

sub podify_deprecated_by
{
	my $package       = shift;
	my @deprecated_by = @_;

	return undef unless scalar @deprecated_by;

	my $str = "$package has been marked as deprecated, and should not be "
	        . "used in newly written code.\n\n";

	# create the deprecated for list
	$str .= "You should use "
	      . join (', ',
	              map {
			if (/^\s*L</) {
				$_;
			}
			else {
				"L<$_>";
			}
		      } @deprecated_by)
	      . " instead of $package.\n";

	return $str;
}

sub podify_enums_and_flags
{
	my $pkgdata = shift;
	my $package = shift;
	
	my %types = ();
	
	my $name;
	my $pod;
	my %info = ();
	foreach (@{$pkgdata->{enums}})
	{
		$name = convert_type ($_->{name});
			
		$pod = $_->{pod};
		shift @{ $pod->{lines} };
		pop @{ $pod->{lines} } if $pod->{lines}[-1] =~ /^=cut/;

		$info{$name} = {
			type => $_->{type},
			pod  => $pod->{lines},
		};
		$types{$name}++;
	}

	foreach my $xsub (@{$pkgdata->{xsubs}})
	{
		if ($xsub->{return_type})
		{
			foreach my $ret (@{$xsub->{return_type}})
			{
				$name = convert_type ($ret);
				$types{$name}++;
			}
		}
		if ($xsub->{args})
		{
			foreach my $arg (@{$xsub->{args}})
			{
				if ($arg->{type})
				{
					$name = convert_type ($arg->{type});
					$types{$name}++;
				}
			}
		}
	}

	if ($package)
	{
		my @props;
		eval { @props = Glib::Object::list_properties($package); 1; };
		foreach my $prop (@props)
		{
			next unless ($prop->{type});
			next unless $prop->{owner_type} eq $package;
			$name = convert_type ($prop->{type});
			$types{$name}++;
		}
		
		my @sigs;
		eval { @sigs = Glib::Type->list_signals ($package); 1; };
		foreach my $sig (@sigs)
		{
			if ($sig->{return_type})
			{
				$name = convert_type ($sig->{return_type});
				$types{$name}++;
			}
			foreach (@{$sig->{param_types}})
			{
				next unless ($_);
				$name = convert_type ($_);
				$types{$name}++;
			}
		}
	}

	my $ret = '';
	foreach (sort keys %types)
	{
		s/\s.*//;

		next if $_ eq 'Glib::Enum' || $_ eq 'Glib::Flags';

		my $values_pod = podify_values ($_);

		if ($values_pod || exists $info{$_})
		{
			my $type = UNIVERSAL::isa ($_, 'Glib::Flags') ?
					'flags' : 'enum';
			$ret .= "=head2 $type $_\n\n";
			$ret .= join ("\n", @{$info{$_}{pod}}) . "\n\n"
				if ($info{$_}{pod});
			$ret .= podify_values ($_) . "\n";
		}
	}
	
	return $ret;
}


=item $string = podify_pods ($pods, $position)

Helper function to allow specific placement of generic pod within the auto
generated pages. Pod sections starting out with =for position XXX, where XXX
is one of the following will be placed at a specified position. In the case of
pod that is to be placed after a particular section that doesn't exist, that
pod will be still be placed there.

This function is called at all of the specified points through out the process
of generating pod for a page. Any pod matching the I<position> passed will be
returned, undef if no matches were found.  If I<position> is undef all pods
without sepcific postion information will be returned. I<pods> is a reference
to an array of pod hashes.

=over

=item * SYNOPSIS

After the NAME section

=item * DESCRIPTION

After the SYNOPSIS section.

=item * post_hierarchy

After the HIERARCHY section.

=item * post_interfaces

After the INTERFACE section.

=item * post_methods

After the METHODS section.

=item * post_properties

After the PROPERTIES section.

=item * post_signals

After the SIGNALS section.

=item * post_enums

After the ENUMS AND FLAGS section.

=item * SEE_ALSO

Replacing the autogenerated SEE ALSO section completely.

=item * COPYRIGHT

Replacing the autogenerated COPYRIGHT section completely.

=back

=cut

sub podify_pods
{
	my $pods = shift;
	my $position = shift;

	my $ret = '';

	if ($position)
	{
		foreach (@$pods)
		{
			$ret .= join ("\n", @{$_->{lines}})."\n\n"
				if (exists ($_->{position}) and 
				    $_->{position} eq $position);
		}
	}
	else
	{
		foreach (@$pods)
		{
			$ret .= join ("\n", @{$_->{lines}})."\n\n"
				unless ($_->{position});
		}
	}
	return $ret ne '' ? $ret : undef;
}

=item $string = podify_ancestors ($packagename)

Pretty-prints the ancestry of I<$packagename> from the Glib type system's
point of view.  This uses Glib::Type->list_ancestors; see that function's
docs for an explanation of why that's different from looking at @ISA.

Returns the new text as a string, or undef if I<$packagename> is not a
registered GType.

=cut

sub podify_ancestors {
	my @anc;
	eval { @anc = Glib::Type->list_ancestors (shift); 1; };
	return undef unless (@anc or not $@);

	my $parents = [ reverse @anc ];

	my $depth = 0;
	my $str = '  '.pop(@anc)."\n";
	foreach (reverse @anc) {
		$str .= "  " . "     "x$depth . "+----$_\n";
		$depth++;
	}
	$str .= "\n";

	return ($str, $parents);
}

=item $string = podify_interfaces ($packagename)

Pretty-print the list of GInterfaces that I<$packagename> implements.
Returns the text as a string, or undef if the type implements no interfaces.

=cut

sub podify_interfaces {
	my @int;
	eval { @int = Glib::Type->list_interfaces (shift); 1; };
	return undef unless (@int or not defined ($@));
	return '  '.join ("\n  ", @int)."\n\n";
}

=item $string = podify_methods ($packagename)

Call C<xsub_to_pod> on all the xsubs under the key I<$packagename> in the
data extracted by xsdocparse.pl.

Returns the new text as a string, or undef if there are no xsubs in
I<$packagename>.

=cut

sub podify_methods
{
	my $package = shift;
	my $xsubs = shift;
	return undef unless $xsubs && @$xsubs;
	# we will be re-using $package from here on out.

	my $str = '';
	my $nfound = 0;
	my $nused  = 0;
	my $method;

	# based on rm's initial thought and then code/ideas by Marc 'HE'
	# Brockschmidt, and Peter Haworth
	@$xsubs = sort { 
		my ($at, $bt);
		for ($at=$a->{symname}, $bt=$b->{symname})
		{
			# remove prefixes
			s/^.+:://;
			# new's goto the front
			s/^new/\x00/;
			# group set's/get'ss
			s/^(get|set)_(.+)/$2_$1/;
			# put \<set\>'s with \<get\>'s
			s/^(get|set)$/get_$1/;
		}
		# now actually do the sorting compare
		$at cmp $bt; 
	} @$xsubs;

	#$str .= "=over\n\n";
	foreach (@$xsubs) {
		# skip if the method is hidden
		next if ($_->{hidden});
		
		$_->{symname} =~ m/^(?:([\w:]+)::)?([\w]+)$/;
		$package = $1 || $_->{package};
		$method = $2;

		# skip DESTROY altogether
		next if $method eq 'DESTROY';

		++$nfound;

		# don't document it if we can't actually call it.
		if ($package->can ($method)) {
			$str .= xsub_to_pod ($_, '=head2');
			++$nused;
		} else {
			# this print should only be temporary
			print STDERR "missing: $package->$method\n";
		}
	}
	#$str .= "=back\n\n";

	if ($nused == 0) {
		# no xsubs were used.
		if ($nfound > 0) {
			# but some were found and not used.  
			# say something to that effect.
			print STDERR "No methods found for $package\n";
			$str = "

Some methods defined for $package are not available in the particular
library versions against which this module was compiled. 

";
		} else {
			# no methods found, nothing to say
			$str = undef;
		}
	}
			
	$str;
}

=item $string = podify_see_alsos (@entries)

Creates a list of links to be placed in the SEE ALSO section of the page.
Returns undef if nothing is in the input list.

=cut

sub podify_see_alsos
{
	my @entries = @_;

	return undef unless scalar @entries;
	
	# create the see also list
	join (', ',
		map {
			if (/^\s*L</) {
				$_;
			} else {
				"L<$_>";
			}
		}
		@entries)
	    . "\n";
}

=item $string = get_copyright

Returns a string that will/should be placed on each page.  You can control
the text of this string by calling the class method I<set_copyright>.

If no text has been set, we will attempt to create one for you, using what
has been passed to I<set_year>, I<set_authors>, and I<set_main_mod>.  The
year defaults to the current year, the authors default to
'The Gtk2-Perl Team', and the main mod is empty by default.  You want the
main mod to be set to the main module of your extension for the SEE ALSO
section, and on the assumption that a decent license notice can be found in
that module's doc, we point the reader there.

So, in general, you will want to specify at least one of these, so that you
don't credit your work to us under the LGPL.

To set them do something similar to the following in the first part of your
postamble section in Makefile.PL.  All occurences of <br> in the copyright
are replaced with newlines, to make it easier to put in a multi-line string.

  POD_SET=Glib::GenPod::set_copyright(qq{Copyright 1999 team-foobar<br>LGPL});

Glib::MakeHelper::postamble_docs_full() does this sort of thing for you.

=cut

sub get_copyright
{
	my $str = $COPYRIGHT;
	if (! $str) {
		# construct a default.
		$str = "\nCopyright (C) $YEAR $AUTHORS\n\n";
		$str .= "This software is licensed under the LGPL;"
		     . " see $MAIN_MOD for a full notice.\n"
			if $MAIN_MOD;
	}

	# a way to make returns	
	$str =~ s/<br>/\n/g;
	return $str."\n";
}

sub set_copyright {
	$COPYRIGHT = shift;
}

sub set_year {
	$YEAR = shift;
}

sub set_authors {
	$AUTHORS = shift;
}

sub set_main_mod {
	$MAIN_MOD = shift;
        eval "use $MAIN_MOD";
        die($@) if($@);
}

sub preprocess_pod
{
	my $pod = shift;

	foreach (@{$pod->{lines}})
	{
		# =for include filename
		# =for include !cmd
		if (/^=for\s+include\s+(!)?(.*)$/)
		{
			if ($1)
			{
				chomp($_ = `$2`);
			}
			else
			{
				if (open INC, "<$2")
				{
					local $/ = undef;
					$_ = <INC>;
				}
				else
				{
					carp "\n\nunable to open $2 for inclusion, at ".
					     $pod->{filename}.':'.$pod->{line};
				}
			}
		}
	}
}

=back

=head2 Helpers

=over

=item $perl_type = convert_type ($ctypestring)

Convert a C type name to a Perl type name.

Uses %Glib::GenPod::basic_types to look for some known basic types,
and uses Glib::Type->package_from_cname to look up the registered
package corresponding to a C type name.  If no suitable mapping can
be found, this just returns the input string.

=cut

sub convert_type {
	my $typestr = shift;

	$typestr =~ /^\s*				# leading space
	              (?:const\s+)?			# maybe a const
	              ([:\w]+)				# the name
	              (\s*\*)?				# maybe a star
	              \s*$/x;				# trailing space
	my $ctype   = $1 || '!!';
	if ($ctype eq '!!') {
		warn "Glib::GenPod: Unable to parse type `$typestr´";
	}

	# variant type
	$ctype =~ s/(?:_(ornull|copy|own_ornull|own|noinc_ornull|noinc))$//;
	my $variant = $1 || "";

	my $perl_type;

	if (exists $basic_types{$ctype}) {
		$perl_type = $basic_types{$ctype};

	} elsif ($ctype =~ m/::/) {
		# :: is not valid in GLib type names, so there's no point
		# in asking the GLib type system if it knows this name,
		# because it's probably already a perl type name.
		$perl_type = $ctype;

	} else {
		eval
		{
			$perl_type = Glib::Type->package_from_cname ($ctype);
			1;
		} or do {
			# this warning will have something to do with the
			# package not being registered, a fact which will
			# of interest to a person documenting or developing
			# the documented module, but not to us developing
			# the documentation generator.  thus, this warning
			# doesn't need a line number attribution.
			# let's strip that...
			$@ =~ s/\s*at (.*) line \d+\.$/./;
			warn "$@";
			# ... and fall back gracefully.
			$perl_type = $ctype;
		}
	}

	if ($variant && $variant =~ m/ornull/) {
		$perl_type .= " or undef";
	}

	$perl_type
}


=item $string = xsub_to_pod ($xsub, $sigprefix='')

Convert an xsub hash into a string of pod describing it.  Includes the
call signature, argument listing, and description, honoring special
switches in the description pod (arg and signature overrides).

=cut

sub xsub_to_pod {
	my $xsub = shift;
	my $sigprefix = shift || '';
	my $alias = $xsub->{symname};
	my $str;

	# ensure that if there's pod for this xsub, we have it now.
	# this should probably happen somewhere outside of this function,
	# but, eh...
	my @podlines = ();
	if (defined $xsub->{pod}) {
		@podlines = @{ $xsub->{pod}{lines} };
	}

	# look for annotations in the pod lines.
	# stuff in the pods overrides whatever we'd generate.
	my @signatures = ();
	if (@podlines) {
		# since we're modifying the list while traversing
		# it, go back to front.
		for (my $i = $#podlines ; $i >= 0 ; $i--) {
			if ($podlines[$i] =~ s/^=(for\s+)?signature\s+//) {
				unshift @signatures, $podlines[$i];
				splice @podlines, $i, 1;
			} elsif ($podlines[$i] =~ /^=(?:for\s+)?arg\s+
			                           (\$?[\w.]+)   # arg name
			                           (?:\s*\(([^)]*)\))? # type
			                           \s*
			                           (.*)$/x) { # desc
				# this is a little convoluted, because we
				# need to ensure that the args array and
				# hash exist before using them.  we may be
				# getting an =arg command on something that
				# doesn't list this name in the xsub
				# declaration.
				$xsub->{args} = [] if not exists $xsub->{args};
				my ($a, undef) =
					grep { $_->{name} eq $1 }
				                  @{ $xsub->{args} };
				$a = {}, push @{$xsub->{args}}, $a
					if not defined $a;
				$a->{name} = $1 if not defined $a->{name};
				$a->{desc} = $3;
				if ($2) {
					if ($2 =~ m/^_*hide_*$/i) {
						$a->{hide}++;
					} else {
						$a->{type} = $2;
					}
				}
				# "just eat it!  eat it!  get yourself and
				# egg and beat it!"  -- weird al
				splice @podlines, $i, 1;
			}
		}
	}

	#
	# the call signature(s).
	#
	push @signatures, compile_signature ($xsub)
		unless @signatures;

	foreach (@signatures) {
		s/>(\w+)/>B<$1>/;
		$str .= "$sigprefix $_\n\n";
	}

	#
	# list all the arg types.
	#
	my @args;
	@args = @{ $xsub->{args} } if ($xsub->{args});
	shift @args unless $xsub->{function};

	$str .= "=over\n\n" if @args;
	foreach my $a (@args) {
		my $type;
		next if $a->{hide};
		if ($a->{name} eq '...') {
			$type = 'list';
		} else {
			if (not defined $a->{type}) {
				warn "$alias: no type defined for arg"
				   . " \$$a->{name}\n";
				$type = "(unknown)";
			} else {
				$type = convert_arg_type ($a->{type});
			}
		}
		$str .= "=item * "
		      . fixup_arg_name ($a->{name})
		      . " ($type) "
		      . ($a->{desc} ? $a->{desc} : "")
		      . "\n\n";
	}
	$str .= "=back\n\n" if @args;

	if (@podlines) {
		shift @podlines;
		pop @podlines;
		$str .= join("\n", @podlines)."\n\n";
	}

	$str .= "May croak with a L<Glib::Error> in \$@ on failure.\n\n"
		if ($xsub->{gerror});

	$str .= "This method is deprecated and should not be used in newly written code.\n\n"
		if ($xsub->{deprecated});


	# When there are multiple version guards of the same type, we only want
	# the innermost.
	my %version_conditions;
	my %prefix_to_name = (
		GTK => 'gtk+',
	);
	foreach (@{ $xsub->{preprocessor_conditionals} }) {
		if (m/^\s*(\w+)_CHECK_VERSION\s*\((\d+),\s*(\d+)/) {
			my $lib_name = $prefix_to_name{$1} || lc $1;
			$version_conditions{$lib_name} = "$2.$3";
		}
	}
	foreach my $lib_name (keys %version_conditions) {
		$str .= "Since: $lib_name $version_conditions{$lib_name}\n\n";
	}

	$str
}

=item $string = compile_signature ($xsub)

Given an xsub hash, return a string with the call signature for that
xsub.

=cut

sub compile_signature {
	my $xsub = shift;

	my @args;
	@args = @{ $xsub->{args} } if ($xsub->{args});

	my $call;

	if ($xsub->{function}) {
		$call = $xsub->{symname};
	} else {
		# find the method's short name
		my $method = $xsub->{symname};
		$method =~ s/^(.*):://;

		my $package = $1 || $xsub->{package};

		# methods always eat the first arg as the instance.
		my $instance = shift @args;

		my $obj = defined ($instance->{type})
		        ? '$'.$instance->{name}
			: $package;

		$call = "$obj\-E<gt>$method";
	}

	# compile the arg list string
	my $argstr = join ", ", map {
			fixup_arg_name ($_->{name})
			. (defined $_->{default}
			   ? '='.fixup_default ($_->{default})
			   : '')
		} @args;

	# compile the return list string
	my @outlist = map { $_->{name} } @{ $xsub->{outlist} };
	if (defined $xsub->{return_type}) {
		my @retnames = map { convert_return_type_to_name ($_) }
				@{ $xsub->{return_type} };
		unshift @outlist, @retnames;
	}
	my $retstr = @outlist
	           ? (@outlist > 1
		      ? "(".join (", ", @outlist).")"
		      : $outlist[0]
		     )." = "
		   : (defined $xsub->{codetype} and
		      $xsub->{codetype} eq 'PPCODE'
		      ? 'list = '
		      : ''
		     );
	
	"$retstr$call ".($argstr ? "($argstr)" : "");
}

=item $string = fixup_arg_name ($name)

Prepend a $ to anything that's not the literal ellipsis string '...'.

=cut

sub fixup_arg_name {
	my $name = shift;
	my $sigil = $name eq '...' ? '' : '$';
	return $sigil.$name;
}

=item fixup_default

Mangle default parameter values from C to Perl values.  Mostly, this
does NULL => undef.

=cut

sub fixup_default {
	my $value = shift;
	return (defined ($value) 
	        ? ($value eq 'NULL' ? 'undef' : $value)
		: '');
}

=item convert_arg_type

C type to Perl type conversion for argument types.

=cut

sub convert_arg_type { convert_type (@_) }


=item convert_return_type_to_name

C type to Perl type conversion suitable for return types.

=cut

sub convert_return_type_to_name {
	my $type = convert_type (@_);
	if ($type =~ s/^.*:://) {
		$type = lc $type;
	}
	return $type;
}

sub mkdir_p {
	my $path = shift;
	my @dirs = File::Spec->splitdir ($path);
	my $p = shift @dirs;
	do {
		mkdir $p or die "can't create dir $p: $!\n" unless -d $p;
		$p = File::Spec->catdir ($p, shift @dirs);
	} while (@dirs);
}

sub convert_to_cname {
    my $perlname = shift;
    my $cname = $perlname;
    if($perlname =~ /^Gtk2::Gdk::/) {
        $cname =~ s/^Gtk2::Gdk::/Gdk/;
    } elsif($perlname =~ m/^Gtk2::/) {
        $cname =~ s/^Gtk2::/Gtk/;
    } elsif($perlname =~ m/^Gnome2::Bonobo::/) {
        $cname =~ s/^Gnome2::Bonobo::/Bonobo/;
    } elsif($perlname =~ m/^Gnome2::/) {
        $cname =~ s/^Gnome2::/Gnome/;
    }
    $cname =~ s/:://g;
    my $tmp;
    eval { $tmp = Glib::Type->package_from_cname($cname); };
    if($@ || $tmp ne $perlname) {
        return;
    }
    return $cname;
}

1;
__END__

=back

=head1 SEE ALSO

L<Glib::ParseXSDoc>

=head1 AUTHORS

muppet bashed out the xsub signature generation in a few hours on a wednesday
night when band practice was cancelled at the last minute; he and ross
mcfarland hacked this module together via irc and email over the next few days.

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2003-2004 by the gtk2-perl team

This library is free software; you can redistribute it and/or modify
it under the terms of the Lesser General Public License (LGPL).  For 
more information, see http://www.fsf.org/licenses/lgpl.txt

=cut
